/**
 * Test that projection with a positional operator works with findAndModify
 * when remove=true or new=false, but not when new=true.
 */
(function() {
    'use strict';

    var collName = 'find_and_modify_server6865';
    var t = db.getCollection(collName);
    t.drop();

    /**
     * Asserts that the specified query and projection returns the expected
     * result, using both the find() operation and the findAndModify command.
     *
     *   insert -- document to insert after dropping collection t
     *   cmdObj -- arguments to the findAndModify command
     *
     *   expected -- the document 'value' expected to be returned after the
     *               projection is applied
     */
    function testFAMWorked(insert, cmdObj, expected) {
        t.drop();
        t.insert(insert);

        var res;

        if (!cmdObj['new']) {
            // Test that the find operation returns the expected result.
            res = t.findOne(cmdObj['query'], cmdObj['fields']);
            assert.eq(res, expected, 'positional projection failed for find');
        }

        // Test that the findAndModify command returns the expected result.
        res = t.runCommand('findAndModify', cmdObj);
        assert.commandWorked(res, 'findAndModify command failed');
        assert.eq(res.value, expected, 'positional projection failed for findAndModify');

        if (cmdObj['new']) {
            // Test that the find operation returns the expected result.
            res = t.findOne(cmdObj['query'], cmdObj['fields']);
            assert.eq(res, expected, 'positional projection failed for find');
        }
    }

    /**
     * Asserts that the specified findAndModify command returns an error.
     */
    function testFAMFailed(insert, cmdObj) {
        t.drop();
        t.insert(insert);

        var res = t.runCommand('findAndModify', cmdObj);
        assert.commandFailed(res, 'findAndModify command unexpectedly succeeded');
    }

    //
    // Delete operations
    //

    // Simple query that uses an inclusion projection.
    testFAMWorked(
        {_id: 42, a: [1, 2], b: 3},
        {query: {_id: 42}, fields: {_id: 0, b: 1}, remove: true},
        {b: 3}
    );

    // Simple query that uses an exclusion projection.
    testFAMWorked(
        {_id: 42, a: [1, 2], b: 3, c: 4},
        {query: {_id: 42}, fields: {a: 0, b: 0}, remove: true},
        {_id: 42, c: 4}
    );

    // Simple query that uses $elemMatch in the projection.
    testFAMWorked(
        {_id: 42, b: [{name: 'first', value: 1},
                      {name: 'second', value: 2},
                      {name: 'third', value: 3}]
        },
        {query: {_id: 42}, fields: {b: {$elemMatch: {value: 2}}}, remove: true},
        {_id: 42, b: [{name: 'second', value: 2}]}
    );

    // Query on an array of values while using a positional projection.
    testFAMWorked(
        {_id: 42, a: [1, 2]},
        {query: {a: 2}, fields: {'a.$': 1}, remove: true},
        {_id: 42, a: [2]}
    );

    // Query on an array of objects while using a positional projection.
    testFAMWorked(
        {_id: 42, b: [{name: 'first', value: 1},
                      {name: 'second', value: 2},
                      {name: 'third', value: 3}]
        },
        {query: {_id: 42, 'b.name': 'third'}, fields: {'b.$': 1}, remove: true},
        {_id: 42, b: [{name: 'third', value: 3}]}
    );

    // Query on an array of objects while using a position projection.
    // Verifies that the projection {'b.$.value': 1} is treated the
    // same as {'b.$': 1}.
    testFAMWorked(
        {_id: 42, b: [{name: 'first', value: 1},
                      {name: 'second', value: 2},
                      {name: 'third', value: 3}]
        },
        {query: {_id: 42, 'b.name': 'third'}, fields: {'b.$.value': 1}, remove: true},
        {_id: 42, b: [{name: 'third', value: 3}]}
    );

    // Query on an array of objects using $elemMatch while using an inclusion projection.
    testFAMWorked(
        {_id: 42, a: 5, b: [{name: 'john', value: 1},
                            {name: 'jess', value: 2},
                            {name: 'jeff', value: 3}]
        },
        {
            query: {b: {$elemMatch: {name: 'john', value: {$lt: 2}}}},
            fields: {_id: 0, a: 5},
            remove: true
        },
        {a: 5}
    );

    // Query on an array of objects using $elemMatch while using the positional
    // operator in the projection.
    testFAMWorked(
        {_id: 42, b: [{name: 'john', value: 1},
                      {name: 'jess', value: 2},
                      {name: 'jeff', value: 3}]
        },
        {
            query: {b: {$elemMatch: {name: 'john', value: {$lt: 2}}}},
            fields: {_id: 0, 'b.$': 1},
            remove: true
        },
        {b: [{name: 'john', value: 1}]}
    );

    //
    // Update operations with new=false
    //

    // Simple query that uses an inclusion projection.
    testFAMWorked(
        {_id: 42, a: [1, 2], b: 3},
        {query: {_id: 42}, fields: {_id: 0, b: 1}, update: {$inc: {b: 1}}, new: false},
        {b: 3}
    );

    // Simple query that uses an exclusion projection.
    testFAMWorked(
        {_id: 42, a: [1, 2], b: 3, c: 4},
        {query: {_id: 42}, fields: {a: 0, b: 0}, update: {$set: {c: 5}}, new: false},
        {_id: 42, c: 4}
    );

    // Simple query that uses $elemMatch in the projection.
    testFAMWorked(
        {_id: 42, b: [{name: 'first', value: 1},
                      {name: 'second', value: 2},
                      {name: 'third', value: 3}]
        },
        {
            query: {_id: 42},
            fields: {b: {$elemMatch: {value: 2}}},
            update: {$set: {name: '2nd'}},
            new: false
        },
        {_id: 42, b: [{name: 'second', value: 2}]}
    );

    // Query on an array of values while using a positional projection.
    testFAMWorked(
        {_id: 42, a: [1, 2]},
        {query: {a: 2}, fields: {'a.$': 1}, update: {$set: {'b.kind': 'xyz'}}, new: false},
        {_id: 42, a: [2]}
    );

    // Query on an array of objects while using a positional projection.
    testFAMWorked(
        {_id: 42, b: [{name: 'first', value: 1},
                      {name: 'second', value: 2},
                      {name: 'third', value: 3}]
        },
        {
            query: {_id: 42, 'b.name': 'third'},
            fields: {'b.$': 1},
            update: {$set: {'b.$.kind': 'xyz'}},
            new: false
        },
        {_id: 42, b: [{name: 'third', value: 3}]}
    );

    // Query on an array of objects while using $elemMatch in the projection,
    // where the matched array element is modified.
    testFAMWorked(
        {_id: 1, a: [{x: 1, y: 1}, {x: 1, y: 2}]},
        {
            query: {_id: 1},
            fields: {a: {$elemMatch: {x: 1}}},
            update: {$pop: {a: -1}},
            new: false
        },
        {_id: 1, a: [{x: 1, y: 1}]}
    );

    // Query on an array of objects using $elemMatch while using an inclusion projection.
    testFAMWorked(
        {_id: 42, a: 5, b: [{name: 'john', value: 1},
                            {name: 'jess', value: 2},
                            {name: 'jeff', value: 3}]
        },
        {
            query: {b: {$elemMatch: {name: 'john', value: {$lt: 2}}}},
            fields: {_id: 0, a: 5},
            update: {$inc: {a: 6}},
            new: false
        },
        {a: 5}
    );

    // Query on an array of objects using $elemMatch while using the positional
    // operator in the projection.
    testFAMWorked(
        {_id: 42, b: [{name: 'john', value: 1},
                      {name: 'jess', value: 2},
                      {name: 'jeff', value: 3}]
        },
        {
            query: {b: {$elemMatch: {name: 'john', value: {$lt: 2}}}},
            fields: {_id: 0, 'b.$': 1},
            update: {$set: {name: 'james'}},
            new: false
        },
        {b: [{name: 'john', value: 1}]}
    );

    //
    // Update operations with new=true
    //

    // Simple query that uses an inclusion projection.
    testFAMWorked(
        {_id: 42, a: [1, 2], b: 3},
        {query: {_id: 42}, fields: {_id: 0, b: 1}, update: {$inc: {b: 1}}, new: true},
        {b: 4}
    );

    // Simple query that uses an exclusion projection.
    testFAMWorked(
        {_id: 42, a: [1, 2], b: 3, c: 4},
        {query: {_id: 42}, fields: {a: 0, b: 0}, update: {$set: {c: 5}}, new: true},
        {_id: 42, c: 5}
    );

    // Simple query that uses $elemMatch in the projection.
    testFAMWorked(
        {_id: 42, b: [{name: 'first', value: 1},
                      {name: 'second', value: 2},
                      {name: 'third', value: 3}]
        },
        {
            query: {_id: 42},
            fields: {b: {$elemMatch: {value: 2}}},
            update: {$set: {'b.1.name': '2nd'}},
            new: true
        },
        {_id: 42, b: [{name: '2nd', value: 2}]}
    );

    // Query on an array of values while using a positional projection.
    testFAMFailed(
        {_id: 42, a: [1, 2]},
        {query: {a: 2}, fields: {'a.$': 1}, update: {$set: {'b.kind': 'xyz'}}, new: true}
    );

    // Query on an array of objects while using a positional projection.
    testFAMFailed(
        {_id: 42, b: [{name: 'first', value: 1},
                      {name: 'second', value: 2},
                      {name: 'third', value: 3}]
        },
        {
            query: {_id: 42, 'b.name': 'third'},
            fields: {'b.$': 1},
            update: {$set: {'b.$.kind': 'xyz'}},
            new: true
        }
    );

    // Query on an array of objects while using $elemMatch in the projection.
    testFAMWorked(
        {_id: 42, b: [{name: 'first', value: 1},
                      {name: 'second', value: 2},
                      {name: 'third', value: 3}]
        },
        {
            query: {_id: 42},
            fields: {b: {$elemMatch: {value: 2}}, c: 1},
            update: {$set: {c: 'xyz'}},
            new: true
        },
        {_id: 42, b: [{name: 'second', value: 2}], c: 'xyz'}
    );

    // Query on an array of objects while using $elemMatch in the projection,
    // where the matched array element is modified.
    testFAMWorked(
        {_id: 1, a: [{x: 1, y: 1}, {x: 1, y: 2}]},
        {
            query: {_id: 1},
            fields: {a: {$elemMatch: {x: 1}}},
            update: {$pop: {a: -1}},
            new: true
        },
        {_id: 1, a: [{x: 1, y: 2}]}
    );

    // Query on an array of objects using $elemMatch while using an inclusion projection.
    testFAMWorked(
        {_id: 42, a: 5, b: [{name: 'john', value: 1},
                            {name: 'jess', value: 2},
                            {name: 'jeff', value: 3}]
        },
        {
            query: {b: {$elemMatch: {name: 'john', value: {$lt: 2}}}},
            fields: {_id: 0, a: 5},
            update: {$inc: {a: 6}},
            new: true
        },
        {a: 11}
    );

    // Query on an array of objects using $elemMatch while using the positional
    // operator in the projection.
    testFAMFailed(
        {_id: 42, b: [{name: 'john', value: 1},
                      {name: 'jess', value: 2},
                      {name: 'jeff', value: 3}]
        },
        {
            query: {b: {$elemMatch: {name: 'john', value: {$lt: 2}}}},
            fields: {_id: 0, 'b.$': 1},
            update: {$set: {name: 'james'}},
            new: true
        }
    );

})();
